﻿using Aliyun.OSS;
using Aliyun.OSS.Common;
using System.Collections.Concurrent;
using XYS.Core.Models;
using XYS.Core.Util;
using XYS.Core.Util.Helper;
using XYS.Service.Core.Models;
using XYS.Service.Core.Models.Enum;

namespace XYS.Service.Core.Service
{
    /// <summary>
    /// 阿里云 对象存储
    /// </summary>
    public class AliyunOSSService : IOSSService
    {
        /// <summary>
        /// 对象存储 配置
        /// </summary>
        public OSSSetting OSSSetting { get; }
        private readonly ICache cacheService;

        private static OssClient client = null;

        /// <summary>
        /// 构造
        /// </summary>
        public AliyunOSSService(List<OSSSetting> ossSettings, ICache cacheService)
        {
            // 由于使用了ICO容器，所以理论上在此处 client 始终为null，为了防止有人手动实例化，此处防止了多次创建client
            if (client != null) return;

            this.cacheService = cacheService;

            OSSSetting = ossSettings.FirstOrDefault(x => x.OSSType.ToLower() == "aliyun");
            // 设置最大并发连接数。
            ClientConfiguration.ConnectionLimit = 512;
            ClientConfiguration conf = new()
            {
                // 设置请求失败后最大的重试次数。
                MaxErrorRetry = 3,
                // 设置连接超时时间。
                ConnectionTimeout = (int)TimeSpan.FromMinutes(5).TotalMilliseconds,
                // 开启MD5校验。
                EnalbeMD5Check = false
            };
            client = new OssClient(OSSSetting.Endpoint, OSSSetting.AccessKey, OSSSetting.SecretKey, conf);
        }

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="req">上传参数</param>
        /// <param name="localFilename">本地文件路径</param>
        /// <param name="aclType">OSS 对象 ACL 类型</param>
        /// <returns>文件在OSS中的完整路径</returns>
        public ResultModel<FilePutResult> Put(FilePutRequest req, string localFilename, EnumOSSACLType aclType = EnumOSSACLType.Default)
        {
            if (!File.Exists(localFilename)) return ResultModel<FilePutResult>.Failed("文件不存在");
            if (!BucketExist(req.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            try
            {
                //构造 上传文件出参
                var result = BuildFilePutResult(req, localFilename);

                // 判断文件是否在OSS中存在。
                var exist = client.DoesObjectExist(req.OssBucket, result.OssFileFullPath);
                if (exist)
                {
                    client.SetObjectAcl(req.OssBucket, result.OssFileFullPath, (CannedAccessControlList)aclType);
                    return ResultModel.Successed(result);
                }

                try
                {
                    //上传
                    using var result_put = client.PutObject(req.OssBucket, result.OssFileFullPath, localFilename);
                    if (result_put.HttpStatusCode == System.Net.HttpStatusCode.OK)
                    {
                        if (aclType != EnumOSSACLType.Default) client.SetObjectAcl(req.OssBucket, result.OssFileFullPath, (CannedAccessControlList)aclType);
                        return ResultModel.Successed(result);
                    }

                    var content = result_put.ResponseStream.ReadToString();
                    return ResultModel<FilePutResult>.Failed(content, EnumStatusCode.BadRequest);
                }
                catch (Exception e)
                {
                    return ResultModel<FilePutResult>.Failed(e.ToJson(), EnumStatusCode.InternalServerError);
                }
            }
            catch (Exception e)
            {
                return ResultModel<FilePutResult>.Failed(e.ToJson(), EnumStatusCode.InternalServerError);
            }
        }


        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="req">上传参数</param>
        /// <param name="fileStream">文件流</param>
        /// <param name="fileName">文件扩展名</param>
        /// <param name="aclType">OSS 对象 ACL 类型</param>
        /// <returns>文件在OSS中的完整路径</returns>
        public ResultModel<FilePutResult> Put(FilePutRequest req, Stream fileStream, string fileName, EnumOSSACLType aclType = EnumOSSACLType.Default)
        {
            if (fileStream.Length <= 0) return ResultModel<FilePutResult>.Failed("文件不存在");
            if (!BucketExist(req.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            //copy 到 内存流中
            using var memoryStream = new MemoryStream();
            fileStream.CopyTo(memoryStream);

            //构造 上传文件出参
            var result = BuildFilePutResult(req, memoryStream, fileName);
            //还原位置
            memoryStream.Seek(0, SeekOrigin.Begin);
            // 判断文件是否在OSS中存在。
            var exist = client.DoesObjectExist(req.OssBucket, result.OssFileFullPath);
            if (exist)
            {
                client.SetObjectAcl(req.OssBucket, result.OssFileFullPath, (CannedAccessControlList)aclType);
                return ResultModel.Successed(result);
            }

            using var result_put = client.PutObject(result.OssBucket, result.OssFileFullPath, memoryStream);
            if (result_put.HttpStatusCode == System.Net.HttpStatusCode.OK)
            {
                if (aclType != EnumOSSACLType.Default) client.SetObjectAcl(req.OssBucket, result.OssFileFullPath, (CannedAccessControlList)aclType);
                return ResultModel.Successed(result);
            }

            var content = result_put.ResponseStream.ReadToString();
            return ResultModel<FilePutResult>.Failed(content, EnumStatusCode.BadRequest);
        }

        /// <summary>
        /// 远程上传
        /// </summary>
        /// <param name="req">上传参数</param>
        /// <param name="remoteUrl">文件的远程地址</param>
        /// <param name="aclType">OSS 对象 ACL 类型</param>
        /// <returns></returns>
        public ResultModel<FilePutResult> RemotePut(FilePutRequest req, string remoteUrl, EnumOSSACLType aclType = EnumOSSACLType.Default) => RemotePutNoWait(req, remoteUrl, aclType);

        /// <summary>
        /// 远程上传
        /// </summary>
        /// <param name="req">上传参数</param>
        /// <param name="remoteUrl">文件的远程地址</param>
        /// <param name="aclType">OSS 对象 ACL 类型</param>
        /// <returns></returns>
        public ResultModel<FilePutResult> RemotePutNoWait(FilePutRequest req, string remoteUrl, EnumOSSACLType aclType = EnumOSSACLType.Default)
        {
            if (string.IsNullOrWhiteSpace(remoteUrl)) return ResultModel<FilePutResult>.Failed("文件不存在");
            if (!BucketExist(req.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            var remoteUri = new Uri(remoteUrl);

            string extension = Path.GetExtension(remoteUri.AbsolutePath);
            if (string.IsNullOrWhiteSpace(extension))
                extension = Path.GetExtension(remoteUri.PathAndQuery);

            string tempFileFile = $"{GlobalCoreSetting.RemotePutSetting.TempFilePath}/{DateTime.Now:yyyy-MM-dd}/{Guid.NewGuid()}{extension}";
            try
            {
                var resultDownload = HttpHelper.DownLoadFileAsync(new HttpHelper.HttpRequestModel()
                {
                    Host = $"{remoteUri.Scheme}://{remoteUri.Authority}",
                    Path = remoteUri.PathAndQuery,
                    Timeout = TimeSpan.FromMinutes(3)
                }, tempFileFile).Result;
                if (!resultDownload.Succeeded) return ResultModel<FilePutResult>.Failed(resultDownload.Error);

                //上传
                return PutNoWait(req, tempFileFile, aclType);
            }
            catch (Exception e)
            {
                return ResultModel<FilePutResult>.Failed(e.ToJson(), EnumStatusCode.InternalServerError);
            }
            //删除临时文件
            finally { try { if (File.Exists(tempFileFile)) File.Delete(tempFileFile); } catch { } }
        }

        /// <summary>
        /// 远程上传
        /// </summary>
        /// <param name="req">上传参数</param>
        /// <param name="remoteUrl">文件的远程地址</param>
        /// <param name="aclType">OSS 对象 ACL 类型</param>
        /// <returns></returns>
        public ResultModel<FilePutResult> RemotePutWait(FilePutRequest req, string remoteUrl, EnumOSSACLType aclType = EnumOSSACLType.Default)
        {
            if (string.IsNullOrWhiteSpace(remoteUrl)) return ResultModel<FilePutResult>.Failed("文件不存在");
            if (!BucketExist(req.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            var remoteUri = new Uri(remoteUrl);

            string extension = Path.GetExtension(remoteUri.AbsolutePath);
            if (string.IsNullOrWhiteSpace(extension))
                extension = Path.GetExtension(remoteUri.PathAndQuery);

            string tempFileFile = $"{GlobalCoreSetting.RemotePutSetting.TempFilePath}/{DateTime.Now:yyyy-MM-dd}/{Guid.NewGuid()}{extension}";
            try
            {
                var resultDownload = HttpHelper.DownLoadFileAsync(new HttpHelper.HttpRequestModel()
                {
                    Host = $"{remoteUri.Scheme}://{remoteUri.Authority}",
                    Path = remoteUri.PathAndQuery,
                    Timeout = TimeSpan.FromMinutes(3)
                }, tempFileFile).Result;
                if (!resultDownload.Succeeded) return ResultModel<FilePutResult>.Failed(resultDownload.Error);

                //上传
                return Put(req, tempFileFile, aclType);
            }
            catch (Exception e)
            {
                return ResultModel<FilePutResult>.Failed(e.ToJson(), EnumStatusCode.InternalServerError);
            }
            //删除临时文件
            finally { try { if (File.Exists(tempFileFile)) File.Delete(tempFileFile); } catch { } }
        }




        /// <summary>
        /// 上传文件 不等待上传完成（后台上传），直接返回结果
        /// </summary>
        /// <param name="req">上传参数</param>
        /// <param name="localFilename">本地文件路径</param>
        /// <param name="aclType">OSS 对象 ACL 类型</param>
        /// <returns>文件在OSS中的完整路径</returns>
        public ResultModel<FilePutResult> PutNoWait(FilePutRequest req, string localFilename, EnumOSSACLType aclType = EnumOSSACLType.Default)
        {
            if (!File.Exists(localFilename)) return ResultModel<FilePutResult>.Failed("文件不存在");
            if (!BucketExist(req.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            // 读取文件内容为字节数组
            byte[] fileBytes = File.ReadAllBytes(localFilename);
            if (fileBytes.Length <= 0) return ResultModel<FilePutResult>.Failed("文件大小为0");
            Task.Run(() =>
            {
                EnumOSSACLType objectACLType = aclType;
                using MemoryStream memoryStream = new(fileBytes);
                Put(req, memoryStream, Path.GetFileName(localFilename), objectACLType);
            });

            var result = BuildFilePutResult(req, localFilename);
            return ResultModel<FilePutResult>.Success(result);
        }




        /// <summary>
        /// 构建 上传文件出参
        /// </summary>
        /// <param name="req"></param>
        /// <param name="memoryStream"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private static FilePutResult BuildFilePutResult(FilePutRequest req, MemoryStream memoryStream, string fileName)
        {
            //还原位置
            memoryStream.Seek(0, SeekOrigin.Begin);
            //获取 文件MD5
            var fileMD5 = EncryptHelper.MD5Stream(memoryStream);
            string ossFilePath = $"{req.OssFilePath.TrimEnd('/')}/{fileMD5}{Path.GetExtension(fileName)}";

            return new FilePutResult()
            {
                OssBucket = req.OssBucket,
                OssFileFullPath = ossFilePath,
                OssFileSize = memoryStream.Length,
                FileMD5 = fileMD5
            };
        }

        /// <summary>
        /// 构造 上传文件出参
        /// </summary>
        /// <param name="req"></param>
        /// <param name="localFilename"></param>
        /// <returns></returns>
        private static FilePutResult BuildFilePutResult(FilePutRequest req, string localFilename)
        {
            FileInfo fileInfo = new(localFilename);
            //获取 文件MD5
            var fileMD5 = EncryptHelper.MD5Stream(localFilename);
            string ossFilePath = $"{req.OssFilePath.TrimEnd('/')}/{fileMD5}{fileInfo.Extension}";

            return new FilePutResult()
            {
                OssBucket = req.OssBucket,
                OssFileFullPath = ossFilePath,
                OssFileSize = fileInfo.Length,
                FileMD5 = fileMD5
            };
        }



        /// <summary>
        /// 获取文件连接
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        public ResultModel<string> GeneratePresignedUri(GeneratePresignedUriReq req)
        {
            var result = GeneratePresignedUri(new List<Models.GeneratePresignedUriRequest>() { new() { Id = Guid.NewGuid().ToString(), Request = req } });
            if (!result.Succeeded) return ResultModel<string>.Failed(result.Error);

            return ResultModel<string>.Success(result.Data?.FirstOrDefault()?.Url);
        }



        /// <summary>
        /// 获取文件连接
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        public ResultModel<List<GeneratePresignedUriResult>> GeneratePresignedUri(List<Models.GeneratePresignedUriRequest> req)
        {
            var result = new List<GeneratePresignedUriResult>();
            var map = req.GroupBy(x => x.Request.OssBucket)
                .ToDictionary(e => e.Key, x => x.ToList());

            // 设置签名URL过期时间，默认值为3600秒。
            var expiration = DateTime.Now.Add(GlobalCoreSetting.OSSExpiration <= TimeSpan.Zero ? TimeSpan.FromHours(1) : GlobalCoreSetting.OSSExpiration);

            //遍历桶
            foreach (string bucketName in map.Keys)
            {
                var files = map[bucketName];
                if (!BucketExist(bucketName))
                {
                    //桶不存在的话 不需要去申请地址了
                    result.AddRange(files.Select(file => new GeneratePresignedUriResult() { Id = file.Id }));
                    continue;
                }

                foreach (var file in files)
                {
                    try
                    {
                        // 判断对象是否在OSS中存在。
                        var exist = client.DoesObjectExist(bucketName, file.Request.OssFileFullPath);
                        if (!exist)
                        {
                            result.Add(new GeneratePresignedUriResult() { Id = file.Id });
                            continue;
                        }

                        CannedAccessControlList? fileACL = null;
                        if (file.Request.ACL != null)
                        {
                            fileACL = file.Request.ACL;
                            SetACL(bucketName, file.Request.OssFileFullPath, file.Request.ACL.Value);
                        }
                        else
                        {
                            fileACL = GetACL(bucketName, file.Request.OssFileFullPath);
                        }

                        if (fileACL != null && (fileACL == CannedAccessControlList.PublicRead || fileACL == CannedAccessControlList.PublicReadWrite))
                        {
                            result.Add(new GeneratePresignedUriResult() { Id = file.Id, Url = $"http://{bucketName}.xysip.com/{file.Request.OssFileFullPath}".Replace("-internal.aliyuncs.com/", ".aliyuncs.com/").Replace("http://", "//").Replace("https://", "//") });
                            continue;
                        }
                        var resultObject = client.GeneratePresignedUri(new Aliyun.OSS.GeneratePresignedUriRequest(bucketName, file.Request.OssFileFullPath));
                        result.Add(new GeneratePresignedUriResult() { Id = file.Id, Url = resultObject.ToString().Replace("-internal.aliyuncs.com/", ".aliyuncs.com/").Replace("http://", "//").Replace("https://", "//") });
                    }
                    catch (Exception e)
                    {
                        return ResultModel<List<GeneratePresignedUriResult>>.Failed(e.ToJson(), EnumStatusCode.InternalServerError);
                    }
                }
            }

            return ResultModel<List<GeneratePresignedUriResult>>.Success(result);
        }



        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="req"></param>
        /// <param name="localFilename"></param>
        /// <returns></returns>
        public ResultModel DownLoad(FileDownLoadRequest req, string localFilename)
        {
            if (!BucketExist(req.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");
            if (!client.DoesObjectExist(req.OssBucket, req.OssFilePath)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            //先删除本地文件
            try { if (File.Exists(localFilename)) File.Delete(localFilename); }
            catch { return ResultModel.Failed("本地文件已存在，并且无法覆盖", 400); }

            var directoryName = Path.GetDirectoryName(localFilename);
            if (!Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName);

            var ossObj = client.GetObject(req.OssBucket, req.OssFilePath);
            using var requestStream = ossObj.Content;
            byte[] buf = new byte[10240];
            using var fs = File.Open(localFilename, FileMode.OpenOrCreate);
            var len = 0;
            // 通过输入流将文件的内容读取到文件或者内存中。
            while ((len = requestStream.Read(buf, 0, buf.Length)) != 0)
            {
                fs.Write(buf, 0, len);
            }
            fs.Close();

            return ResultModel.Success;
        }

        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public ResultModel Delete(DeleteOssFileRequest request)
        {
            if (!BucketExist(request.OssBucket)) return ResultModel<FilePutResult>.Failed("文件不存在。");
            if (!client.DoesObjectExist(request.OssBucket, request.OssFileFullPath)) return ResultModel<FilePutResult>.Failed("文件不存在。");

            client.DeleteObject(request.OssBucket, request.OssFileFullPath);
            return ResultModel.Success;
        }



        /// <summary>
        /// 缓存 存在的桶名
        /// </summary>
        private static readonly ConcurrentBag<string> BucketNames = new();

        /// <summary>
        /// 桶是否存在
        /// </summary>
        /// <param name="bucketName"></param>
        /// <returns></returns>
        private bool BucketExist(string bucketName)
        {
            if (BucketNames.Contains(bucketName)) return true;

            string cacheKey = CacheKeysHelper.GetBucketExistCache(OSSSetting, bucketName);

            var isExisted = cacheService.Get<bool?>(cacheKey, null, () =>
            {
                var buckets = client.ListBuckets();
                return buckets.Any(x => x.Name == bucketName);
            });
            if (isExisted == true)
            {
                BucketNames.Add(bucketName);
                return true;
            }

            try
            {
                var buckets = client.ListBuckets();
                isExisted = buckets.Any(x => x.Name == bucketName);
                if (isExisted == true)
                {
                    cacheService.Set(cacheKey, isExisted, null);
                    BucketNames.Add(bucketName);
                    return true;
                }
                return false;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 读取对象的ACL
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public CannedAccessControlList? GetACL(string bucketName, string path)
        {
            try
            {
                AccessControlList acl = client.GetObjectAcl(new GetObjectAclRequest(bucketName, path));
                return acl?.ACL;
            }
            catch { }
            return null;
        }

        /// <summary>
        /// 设置对象的ACL
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="path"></param>
        /// <param name="acl"></param>
        /// <returns></returns>
        public ResultModel SetACL(string bucketName, string path, CannedAccessControlList acl)
        {
            try
            {
                client.SetObjectAcl(new SetObjectAclRequest(bucketName, path, acl));
                return ResultModel.Success;
            }
            catch (Exception e)
            {
                return ResultModel.Failed(e.ToJson(), EnumStatusCode.InternalServerError);
            }
        }
    }
}
